package evl

import (
	"testing"

	"gitcode.com/deyiyangyang/bampoo/lex"
	"gitcode.com/deyiyangyang/bampoo/obj"
	"gitcode.com/deyiyangyang/bampoo/par"
)

func TestLetBasic(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected int64
	}{
		{
			name:     "simple let with one binding",
			input:    "(let ((x 5)) x)",
			expected: 5,
		},
		{
			name:     "let with multiple bindings",
			input:    "(let ((x 10) (y 20)) (+ x y))",
			expected: 30,
		},
		{
			name:     "let with expression in binding",
			input:    "(let ((x (+ 2 3))) x)",
			expected: 5,
		},
		{
			name:     "let with multiple body expressions",
			input:    "(let ((x 5)) (display x) (+ x 10))",
			expected: 15,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Create lexer and parser
			lexer := lex.New(tt.input)
			parser := par.New(lexer)

			// Parse the program
			program := parser.ParseProgram()
			if len(parser.Errors()) > 0 {
				t.Fatalf("parser errors: %v", parser.Errors())
			}

			// Create evaluator
			env := obj.NewEnvironment()
			evaluator := New(env)

			// Evaluate the program
			result, err := evaluator.Eval(program)
			if err != nil {
				t.Fatalf("evaluation error: %v", err)
			}

			// Check result
			intResult, ok := result.(*obj.IntegerValue)
			if !ok {
				t.Fatalf("expected IntegerValue, got %T", result)
			}
			if intResult.Value != tt.expected {
				t.Errorf("expected %d, got %d", tt.expected, intResult.Value)
			}
		})
	}
}

func TestLetVariableScope(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected int64
	}{
		{
			name:     "let shadows outer variable",
			input:    "(define x 100) (let ((x 5)) x)",
			expected: 5,
		},
		{
			name:     "let binding can reference outer variable",
			input:    "(define x 10) (let ((y (+ x 5))) y)",
			expected: 15,
		},
		{
			name:     "nested let expressions",
			input:    "(let ((x 5)) (let ((x 10)) x))",
			expected: 10,
		},
		{
			name:     "outer variable unchanged after let",
			input:    "(define x 100) (let ((x 5)) (+ x 1)) x",
			expected: 100,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Create lexer and parser
			lexer := lex.New(tt.input)
			parser := par.New(lexer)

			// Parse the program
			program := parser.ParseProgram()
			if len(parser.Errors()) > 0 {
				t.Fatalf("parser errors: %v", parser.Errors())
			}

			// Create evaluator
			env := obj.NewEnvironment()
			evaluator := New(env)

			// Evaluate the program
			result, err := evaluator.Eval(program)
			if err != nil {
				t.Fatalf("evaluation error: %v", err)
			}

			// Check result
			intResult, ok := result.(*obj.IntegerValue)
			if !ok {
				t.Fatalf("expected IntegerValue, got %T", result)
			}
			if intResult.Value != tt.expected {
				t.Errorf("expected %d, got %d", tt.expected, intResult.Value)
			}
		})
	}
}

func TestLetBindingEvaluation(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected int64
	}{
		{
			name:     "bindings evaluated in parallel (cannot reference each other)",
			input:    "(define x 10) (let ((x 5) (y (+ x 5))) y)",
			expected: 15,
		},
		{
			name:     "bindings use outer scope variables",
			input:    "(define x 100) (let ((y (+ x 50))) y)",
			expected: 150,
		},
		{
			name:     "complex binding expressions",
			input:    "(let ((a (+ 1 2)) (b (* 3 4))) (+ a b))",
			expected: 15,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Create lexer and parser
			lexer := lex.New(tt.input)
			parser := par.New(lexer)

			// Parse the program
			program := parser.ParseProgram()
			if len(parser.Errors()) > 0 {
				t.Fatalf("parser errors: %v", parser.Errors())
			}

			// Create evaluator
			env := obj.NewEnvironment()
			evaluator := New(env)

			// Evaluate the program
			result, err := evaluator.Eval(program)
			if err != nil {
				t.Fatalf("evaluation error: %v", err)
			}

			// Check result
			intResult, ok := result.(*obj.IntegerValue)
			if !ok {
				t.Fatalf("expected IntegerValue, got %T", result)
			}
			if intResult.Value != tt.expected {
				t.Errorf("expected %d, got %d", tt.expected, intResult.Value)
			}
		})
	}
}

func TestLetErrorCases(t *testing.T) {
	tests := []struct {
		name        string
		input       string
		shouldError bool
		errorMsg    string
		expected    int64
	}{
		{
			name:        "empty let with no bindings",
			input:       "(let () 5)",
			shouldError: false,
			expected:    5,
		},
		{
			name:        "let with no body",
			input:       "(let ((x 5)))",
			shouldError: false,
			expected:    0, // nil value
		},
		{
			name:        "undefined variable in binding",
			input:       "(let ((x undefined-var)) x)",
			shouldError: true,
			errorMsg:    "undefined symbol",
			expected:    0,
		},
		{
			name:        "invalid binding syntax",
			input:       "(let (x 5) x)",
			shouldError: true,
			errorMsg:    "expected '(' for binding",
			expected:    0,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Create lexer and parser
			lexer := lex.New(tt.input)
			parser := par.New(lexer)

			// Parse the program
			program := parser.ParseProgram()
			if len(parser.Errors()) > 0 {
				if tt.shouldError {
					// Expected error during parsing
					return
				}
				t.Fatalf("unexpected parser errors: %v", parser.Errors())
			}

			// Create evaluator
			env := obj.NewEnvironment()
			evaluator := New(env)

			// Evaluate the program
			result, err := evaluator.Eval(program)

			if tt.shouldError {
				if err == nil {
					t.Fatalf("expected error but got none")
				}
				if tt.errorMsg != "" && !contains(err.Error(), tt.errorMsg) {
					t.Errorf("expected error containing %q, got %q", tt.errorMsg, err.Error())
				}
			} else {
				if err != nil {
					t.Fatalf("unexpected error: %v", err)
				}
				if tt.expected != 0 {
					intResult, ok := result.(*obj.IntegerValue)
					if !ok {
						t.Fatalf("expected IntegerValue, got %T", result)
					}
					if intResult.Value != tt.expected {
						t.Errorf("expected %d, got %d", tt.expected, intResult.Value)
					}
				}
			}
		})
	}
}